package sdkTest;

import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import org.junit.Before;
import org.junit.Test;

import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.Event;
import com.alibaba.nacos.api.naming.listener.EventListener;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;


/**
 * 通过Java代码的方式实现与NacosDiscovery服务注册中心的编程式相互
 * 需要依赖项：
	<dependency>
		<groupId>com.alibaba.nacos</groupId>
		<artifactId>nacos-client</artifactId>		
	</dependency>
 */
public class NacosDiscoveryByCoding {
	// Nacos的服务地址
	String serverAddr = "localhost:8848";
	// 文件所属的组别
	String group = "DEFAULT_GROUP";
	
	NamingService naming;
	
	/**
	 * 初始化NamingService，Nacos的所有API操作都与NamingService相关
	 * @throws NacosException
	 */
	@Before
	public void init() throws Exception {
		// 创建一个properties文件，并配置Nacos的服务地址
		Properties prop = new Properties();
		/*
		 * 如果是nacos单机，直接传入ip地址
		 * 如果是nacos集群，可配置多个IP地址，使用','号隔开
		 */
		prop.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
		//prop.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8850,127.0.0.1:8860,127.0.0.1:8870");
		// 通过传入的properties配置文件，创建一个NamingService配置中心服务
		naming = NamingFactory.createNamingService(prop);
	}
	
	/**
	 * 通过Java代码的形式向NacosDiscovery注册服务
	 * !：对于同一个服务名，一个NamingService只能注册一个服务实例。如果需要再次注册，则需要重新创建一个新的NamingService对象
	 *    不同的服务名，注册服务实例不受NamingService的影响
	 * @throws NacosException
	 */
	@Test
	public void registerInstance() throws Exception {
		/********************************************************
		 * 方式1、使用ip、port进行服务实例注册 
		 * 		默认注册到cluster：DEFAULT；group：DEFAULT_GROUP
		 ********************************************************/
		// 默认Cluster、默认Group注册
		naming.registerInstance("service_1", "1.1.1.1", 8888);
		//!：对于同一个服务名，一个NamingService只能注册一个服务实例。如果需要再次注册，则需要重新创建一个新的NamingService对象
		//   不同的服务名，注册服务实例不受NamingService的影响
//		naming.registerInstance("service_1", "1.1.1.11", 8888);
		NamingFactory.createNamingService(serverAddr).registerInstance("service_1", "1.1.1.11", 8888);
		// 指定Cluster、默认Group注册
		naming.registerInstance("service_2", "2.2.2.2", 8888, "cluster2");
		// 默认Cluster、指定Group注册
		naming.registerInstance("service_3", "group_3", "3.3.3.3", 8888);
		// 指定Cluster、指定Group注册
		naming.registerInstance("service_4", "group_4", "4.4.4.4", 8888, "cluster4");
		

		/********************************************************
		 * 方式2、使用Instance实例进行服务实例注册 
		 * 		默认注册到cluster：DEFAULT；group：DEFAULT_GROUP
		 ********************************************************/
		// 构造Instance服务实例，可选择是否指定Cluster
		Instance instance = new Instance();
		// 实例的IP，提供给消费者进行通信的地址；
		instance.setIp("11.11.11.11");
		// 实例的端口，提供给消费者访问的端口；
		instance.setPort(9999);
		// 实例的健康状态，默认'true'。true：健康实例；false：非健康实例
		instance.setHealthy(true);
		// 实例的权重，当前实例的权限，默认'1.0D'；
		instance.setWeight(-9999);
		// 实例是否准备好接收请求，默认'true'。true：可接收请求；false：不可接收请求
		instance.setEnabled(true);
		// 实例是否为临时实例，默认为'true'。true：临时实例；false：持久实例 
		instance.setEphemeral(true);
		// 指定当前服务实例的Cluster，不指定默认在'DEFAULT'
		instance.setClusterName("clusterInstance");
		// 为当前服务实例绑定'Meta原数据'
		instance.setMetadata(new HashMap<>() {{
			put("site", "et2");
		}});
		// 默认Group注册Instance实例
		naming.registerInstance("service_instance_1", instance);
		// 指定Group注册Instance实例
		instance.setHealthy(false);
		naming.registerInstance("service_instance_2", "group_instance", instance);
		
		System.in.read(); // 需要阻塞程序，否则程序执行结束退出线程，会导致Nacos的Client客户端心跳检测不通过从而强制下线实例
	}
	
	
	/**
	 * 通过Java代码的形式向NacosDiscovery注销服务
	 * @throws NacosException
	 */
	@Test
	public void deregisterInstance() throws Exception {
		/********************************************************
		 * 方式1、使用ip、port进行服务实例注销
		 * 		默认从cluster：DEFAULT；group：DEFAULT_GROUP注销
		 ********************************************************/
		// 默认Cluster、默认Group注销
		naming.deregisterInstance("service_1", "1.1.1.1", 8888);
		// 指定Cluster、默认Group注销
		naming.deregisterInstance("service_2", "2.2.2.2", 8888, "cluster2");
		// 默认Cluster、指定Group注销
		naming.deregisterInstance("service_3", "group_3", "3.3.3.3", 8888);
		// 指定Cluster、指定Group注销
		naming.deregisterInstance("service_4", "group_4", "4.4.4.4", 8888, "cluster4");

		/********************************************************
		 * 方式2、使用Instance实例进行服务实例注销 
		 * 		默认注册到cluster：DEFAULT；group：DEFAULT_GROUP
		 ********************************************************/
		// 构造Instance服务实例，可选择是否指定Cluster
		Instance instance = new Instance();
		// 实例的IP，提供给消费者进行通信的地址；
		instance.setIp("11.11.11.11");
		// 实例的端口，提供给消费者访问的端口；
		instance.setPort(9999);
		// 指定当前服务实例的Cluster，不指定默认在'DEFAULT'
		instance.setClusterName("clusterInstance");
		/*
		 * 实例是否为临时实例，默认为'true'。true：临时实例；false：持久实例
		 * 如果是通过API进行持久实例的注销删除，则必须要设置该项值为false，否则注销无效(因为Instace.equals()无法判定为同一个)
		 */
		instance.setEphemeral(false);
		// Meta原数据绑定
		instance.setMetadata(new HashMap<>() {{
			put("site", "et2");
		}});
		// 默认Group注销Instance实例
		naming.deregisterInstance("service_instance_1", instance);
		// 指定Group注销Instance实例
		naming.deregisterInstance("service_instance_2", "group_instance", instance);
	}


	/**
	 * 通过Java代码的形式从NacosDiscovery获取指定服务的全部实例
	 * 相关参数说明：
	 * 	serviceName：
	 * 		服务的服务名
	 * 	group：
	 * 		服务的所属分组，默认值'DEFAULT_GROUP'
	 * 	clusters：
	 * 		服务实例的所属集群，类型为集群名字的List，默认值'空list'(查询当前服务的所有集群Instance实例)。
	 * 	subscribe：
	 * 		是否订阅(支持服务器主动推送)，默认值'true'(支持订阅)
	 * 【!!!:subscribe相关说明】
	 * 		nacos服务发现有2种方式：
	 * 			1.客户端通过API主动调用服务端拉取服务实例列表；
	 * 			2.服务订阅模式，客户端通过注册listener，当nacos服务端服务实例变化时，服务端通过udp协议主动把变更实例列表推送给客户端。
	 * 		如果开启订阅(subscribe=true)，客户端在主动拉取实例列表的同时(首次获取)，会与nacos服务器建立udp连接，当服务器实例有状态变化时，会通过udp把实例列表主动推送给客户端。
	 * @throws NacosException
	 */
	@Test
	public void getAllInstances() throws Exception {
		// 创建一个Nacos的Subscriber订阅，用于检测InstancesChangeEvent实例变更事件
		NotifyCenter.registerSubscriber( new Subscriber<InstancesChangeEvent>() {
			@Override
			public void onEvent(InstancesChangeEvent event) {
				try {
					System.out.println( new ObjectMapper().writeValueAsString(event) );
				} catch (JsonProcessingException e) {
					e.printStackTrace();
				}
			}
			@Override
			public Class<? extends com.alibaba.nacos.common.notify.Event> subscribeType() {
				 return InstancesChangeEvent.class;
			}
		} );
		
		/*
		 * getAllInstances(serviceName)
		 * 通过servicename(服务名)获取服务实例
		 * 	底层其他参数默认使用
		 * 		group：DEFAULT_GROUP
		 * 		clusters：[]任意集群
		 * 		subscribe：true
		 */
		List<Instance> list_1 = naming.getAllInstances("service_1");
		System.err.println( "1.通过服务名(servicename)获取'service_1'的服务实例：\r\n" + list_1);
		
		/*
		 * getAllInstances(serviceName, subscribe)
		 * 通过servicename(服务名)获取服务实例，并通过subscribe(是否开启订阅) 
		 * 	底层其他参数默认使用
		 * 		group：DEFAULT_GROUP
		 * 		clusters：[]任意集群
		 */
		List<Instance> list_2 = naming.getAllInstances("service_2", true);
		System.err.println( "2.通过服务名(servicename)获取服务实例，并通过subscribe(是否开启订阅)：\r\n" + list_2 );
		
		/*
		 * getAllInstances(serviceName, clusters)
		 * 通过servicename(服务名)、clusters(服务所属集群)获取服务实例
		 * 	底层其他参数默认使用
		 * 		group：DEFAULT_GROUP
		 * 		subscribe：true
		 */
		List<Instance> list_3 = naming.getAllInstances("service_instance_1", List.of("clusterInstance"));
		System.err.println( "3.通过服务名(servicename)、集群分组(cluster)获取服务实例：\r\n" + list_3 );
		
		/*
		 * getAllInstances(serviceName, groupName)
		 * 通过servicename(服务名)、group(服务分组)获取服务实例
		 * 	底层其他参数默认使用
		 * 		clusters：[]任意集群
		 * 		subscribe：true
		 */
		List<Instance> list_4 = naming.getAllInstances("service_4", "group_4");
		System.err.println( "4.通过服务名(servicename)、服务分组(group)获取服务实例：\r\n" + list_4 );
		
		/*
		 * getAllInstances(serviceName, groupName, subscribe)
		 * 通过servicename(服务名)、group(服务分组)获取服务实例，并通过subscribe(是否开启订阅) 
		 * 	底层其他参数默认使用
		 * 		group：DEFAULT_GROUP
		 * 		clusters：[]任意集群
		 */
		List<Instance> list_5 = naming.getAllInstances("service_4", "group_4", true);
		System.err.println( "5.通过服务名(servicename)、服务分组(group)获取服务实例，并通过subscribe(是否开启订阅)：\r\n" + list_5 );
		
		/*
		 * getAllInstances(serviceName, clusters, subscribe)
		 * 通过servicename(服务名)、clusters(服务所属集群)获取服务实例，并通过subscribe(是否开启订阅) 
		 * 	底层其他参数默认使用
		 * 		group：DEFAULT_GROUP
		 */
		List<Instance> list_6 = naming.getAllInstances("service_instance_1", List.of("clusterInstance"), false);
		System.err.println( "6.通过服务名(servicename)、服务所属集群(clusters)获取服务实例，并通过subscribe(是否开启订阅)：\r\n" + list_6 );
		
		/*
		 * getAllInstances(serviceName, groupName, clusters)
		 * 通过servicename(服务名)、group(服务分组)、clusters(服务所属集群)获取服务实例
		 * 	底层其他参数默认使用
		 * 		subscribe：true
		 */
		List<Instance> list_7 = naming.getAllInstances("service_3", "group_3", List.of("DEFAULT"));
		System.err.println( "7.通过服务名(servicename)、服务分组(group)、服务所属集群(Cluster)获取服务实例：\r\n" + list_7);
		
		/*
		 * getAllInstances(String serviceName, String groupName, List<String> clusters, boolean subscribe)
		 * 通过servicename(服务名)、group(服务分组)、clusters(服务所属集群)获取服务实例，并通过subscribe(是否开启订阅) 
		 * 其他所有方法均是重载自该方法，底层实现均是采用默认值传参调用该方法
		 */
		List<Instance> list_8 = naming.getAllInstances("service_4", "group_4", List.of("cluster4"), true);
		System.err.println( "8.通过服务名(servicename)、服务分组(group)、服务所属集群(Cluster)获取服务实例，并通过subscribe(是否开启订阅)：\r\n" + list_8 );
	}
	
	
	/**
	 * 通过Java代码的形式从NacosDiscovery获取指定服务的全部'健康'or'不健康'实例
	 * 相关参数说明：
	 * 	serviceName：
	 * 		服务的服务名
	 * 	group：
	 * 		服务的所属分组，默认值'DEFAULT_GROUP'
	 * 	clusters：
	 * 		服务实例的所属集群，类型为集群名字的List，默认值'空list'(查询当前服务的所有集群Instance实例)。
	 * 	subscribe：
	 * 		是否订阅(支持服务器主动推送)，默认值'true'(支持订阅)
	 * 【!!!:subscribe相关说明】
	 * 		nacos服务发现有2种方式：
	 * 			1.客户端通过API主动调用服务端拉取服务实例列表；
	 * 			2.服务订阅模式，客户端通过注册listener，当nacos服务端服务实例变化时，服务端通过udp协议主动把变更实例列表推送给客户端。
	 * 		如果开启订阅(subscribe=true)，客户端在主动拉取实例列表的同时(首次获取)，会与nacos服务器建立udp连接，当服务器实例有状态变化时，会通过udp把实例列表主动推送给客户端。
	 * @throws NacosException
	 */
	@Test
	public void selectInstances() throws Exception {
		/*
		 * selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe)
		 * 其他所有方法均是重载自该方法，底层实现均是采用默认值传参调用该方法
		 * 
		 */
		List<Instance> selectInstances = naming.selectInstances("service_1", "DEFAULT_GROUP", List.of("DEFAULT"), false, true);
		System.out.println(
			"通过服务名(servicename)、服务分组(group)、服务所属集群(Cluster)获取服务实例，并通过subscribe(是否开启订阅)：\r\n" 
			+ selectInstances 
		);
	}
	
	/**
	 * 通过Java代码的形式从NacosDiscovery获取指定服务的随机(根据权重分配)一个'健康'实例
	 * 相关参数说明：
	 * 	serviceName：
	 * 		服务的服务名
	 * 	group：
	 * 		服务的所属分组，默认值'DEFAULT_GROUP'
	 * 	clusters：
	 * 		服务实例的所属集群，类型为集群名字的List，默认值'空list'(查询当前服务的所有集群Instance实例)。
	 * 	subscribe：
	 * 		是否订阅(支持服务器主动推送)，默认值'true'(支持订阅)
	 * 【!!!:subscribe相关说明】
	 * 		nacos服务发现有2种方式：
	 * 			1.客户端通过API主动调用服务端拉取服务实例列表；
	 * 			2.服务订阅模式，客户端通过注册listener，当nacos服务端服务实例变化时，服务端通过udp协议主动把变更实例列表推送给客户端。
	 * 		如果开启订阅(subscribe=true)，客户端在主动拉取实例列表的同时(首次获取)，会与nacos服务器建立udp连接，当服务器实例有状态变化时，会通过udp把实例列表主动推送给客户端。
	 * @throws NacosException
	 */
	@Test
	public void selectOneHealthyInstance() throws Exception {
		/*
		 * selectOneHealthyInstance(String serviceName, String groupName, List<String> clusters, boolean subscribe)
		 * 其他所有方法均是重载自该方法，底层实现均是采用默认值传参调用该方法
		 */
		Instance selectOneHealthyInstance = naming.selectOneHealthyInstance("service_1", "DEFAULT_GROUP", List.of("DEFAULT"), true);
		System.out.println(
				"通过服务名(servicename)、服务分组(group)、服务所属集群(Cluster)获取服务实例，并通过subscribe(是否开启订阅)：\r\n" 
				+ selectOneHealthyInstance 
			);
	}
	
	
	/**
	 * 为指定dataId配置文件
	 * 	'添加'Listern监听器，监听其的变更操作 
	 * 		如果被监听的配置文件不存在，不会报错
	 * 		如果被监听的配置文件已存在，且在被监听之前有发生过变更，则在添加Listener时会立即收到最近一次变更的回调(无论之前被修改了多少次)
	 * 	或
	 * 	'删除'Listern监听器，取消对配置文件的监听
	 * @throws Exception
	 */
	@Test
	public void add_Or_Remove_Listener() throws Exception {
		// 创建EventListener监听器
		EventListener listener = new EventListener () {
			@Override
			public void onEvent(Event event) {
				try {
					System.out.println( new ObjectMapper().writeValueAsString(event) );
				} catch (JsonProcessingException e) {
					e.printStackTrace();
				}
			}
		};
		
		String serviceName = "service_1", group = "DEFAULT_GROUP";
		List clusters = List.of("DEFAULT");
		
		// 测试让主线程不退出，因为订阅配置是守护线程，主线程退出守护线程就会退出。 正式代码中无需下面代码
		while( true ) {
			int read = System.in.read();
			if( '0' == read ) 
				break;
			if( '1' == read) 
				naming.subscribe(serviceName, group, clusters, listener);
			if( '2' == read ) 
				naming.unsubscribe(serviceName, group, clusters, listener);
		}
	}
}
